/* ----------------------------------------------------------------------------

	pax.date.js Copyright (C) 2006, 2007, 2008 Mikkel Bergmann, Pointful

	Licence
	-------
	
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

	See lgpl.txt for licence details

---------------------------------------------------------------------------- */

/*
	Script: pax.date
		This is the date parsing and manipulation library; the date formatting 
		methods in this script are from the awesome script by Baron Schwartz,
		originally published here:
	
		http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/
	
		Note: The original script has been modified to comply with the PAX namespace
		
	Authors:
		Baron Schwartz <http://www.xaprb.com/>, Mikkel Bergmann <http://www.pointful.com>, 

*/

var pax = pax || {};
pax.date = pax.date || {};

pax.date.parseFunctions = { count: 0 };
pax.date.parseRegexes = [];
pax.date.formatFunctions = { count: 0 };

/*	Method: pax.date.dateFormat
	Sets the expected format for a date object. The format is like the PHP date syntax.
:    
:    Sample date: 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
:    
:    Format  Output      Description
:    ------  ----------  --------------------------------------------------------------
:      d      10         Day of the month, 2 digits with leading zeros
:      D      Wed        A textual representation of a day, three letters
:      j      10         Day of the month without leading zeros
:      l      Wednesday  A full textual representation of the day of the week
:      S      th         English ordinal day of month suffix, 2 chars (use with j)
:      w      3          Numeric representation of the day of the week
:      z      9          The julian date, or day of the year (0-365)
:      W      01         ISO-8601 2-digit week number of year, weeks starting on Monday (00-52)
:      F      January    A full textual representation of the month
:      m      01         Numeric representation of a month, with leading zeros
:      M      Jan        Month name abbreviation, three letters
:      n      1          Numeric representation of a month, without leading zeros
:      t      31         Number of days in the given month
:      L      0          Whether it's a leap year (1 if it is a leap year, else 0)
:      Y      2007       A full numeric representation of a year, 4 digits
:      y      07         A two digit representation of a year
:      a      pm         Lowercase Ante meridiem and Post meridiem
:      A      PM         Uppercase Ante meridiem and Post meridiem
:      g      3          12-hour format of an hour without leading zeros
:      G      15         24-hour format of an hour without leading zeros
:      h      03         12-hour format of an hour with leading zeros
:      H      15         24-hour format of an hour with leading zeros
:      i      05         Minutes with leading zeros
:      s      01         Seconds, with leading zeros
:      O      -0600      Difference to Greenwich time (GMT) in hours
:      T      CST        Timezone setting of the machine running the code
:      Z      -21600     Timezone offset in seconds (negative if west of UTC, positive if east)

	Example:
		(start code)
			<div id='date.format.example1'></div>
			[:.
				var myDate = new Date();
				pax.$('date.format.example1').innerHTML = myDate.dateFormat( 'D M Y' );
			:]
		(end)
		This will display todays date as "DDD MMM YYYY"

*/
pax.date.dateFormat = function( date, format ) {
	if( pax.date.formatFunctions[format] == null )pax.date.createNewFormat( date, format );
	var func = pax.date.formatFunctions[format];
	return pax.date[func]( date );
};

pax.date.createNewFormat = function( date, format ) {
	var funcName = "format" + pax.date.formatFunctions.count++;
	pax.date.formatFunctions[format] = funcName;
	
	var code = "pax.date." + funcName + " = function( date ) { return ";
	var special = false;
	var ch = '';
	
	for( var i = 0; i < format.length; ++i ) {
		ch = format.charAt( i );
		if( ! special && ch == "\\" )special = true;
		else if ( special ) {
			special = false;
			code += "'" + pax.date.escape(ch) + "' + ";
		} else code += pax.date.getFormatCode(ch);
	}
	
	eval( code.substring( 0, code.length - 3 ) + ";}" );
};

pax.date.getFormatCode = function(c) {
	return	(c == 'd')? "pax.date.leftPad(date.getDate(), 2, '0') + ":
			(c == 'D')? "pax.date.dayNames[date.getDay()].substring(0, 3) + ":
			(c == 'j')? "date.getDate() + ":
			(c == 'l')? "pax.date.dayNames[date.getDay()] + ":
			(c == 'S')? "pax.date.getSuffix( date ) + ":
			(c == 'w')? "date.getDay() + ":
			(c == 'z')? "pax.date.getDayOfYear( date ) + ":
			(c == 'W')? "pax.date.getWeekOfYear( date ) + ":
			(c == 'F')? "pax.date.monthNames[date.getMonth()] + ":
			(c == 'm')? "pax.date.leftPad(date.getMonth() + 1, 2, '0') + ":
			(c == 'M')? "pax.date.monthNames[date.getMonth()].substring(0, 3) + ":
			(c == 'n')? "(date.getMonth() + 1) + ":
			(c == 't')? "pax.date.getDaysInMonth( date ) + ":
			(c == 'L')? "(pax.date.isLeapYear( date ) ? 1 : 0) + ":
			(c == 'Y')? "date.getFullYear() + ":
			(c == 'y')? "('' + date.getFullYear()).substring(2, 4) + ":
			(c == 'a')? "(date.getHours() < 12 ? 'am' : 'pm') + ":
			(c == 'A')? "(date.getHours() < 12 ? 'AM' : 'PM') + ":
			(c == 'g')? "((date.getHours() %12) ? date.getHours() % 12 : 12) + ":
			(c == 'G')? "date.getHours() + ":
			(c == 'h')? "pax.date.leftPad((date.getHours() %12) ? date.getHours() % 12 : 12, 2, '0') + ":
			(c == 'H')? "pax.date.leftPad(date.getHours(), 2, '0') + ":
			(c == 'i')? "pax.date.leftPad(date.getMinutes(), 2, '0') + ":
			(c == 's')? "pax.date.leftPad(date.getSeconds(), 2, '0') + ":
			(c == 'O')? "pax.date.getGMTOffset( date ) + ":
			(c == 'T')? "pax.date.getTimezone( date ) + ":
			(c == 'Z')? "(date.getTimezoneOffset() * -60) + ":
			"'" + pax.date.escape(c) + "' + ";
};

pax.date.parseDate = function( input, format ) {
	if( pax.date.parseFunctions[format] == null )pax.date.createParser( format );
	var func = pax.date.parseFunctions[format];
	return pax.date[func]( input );
};

pax.date.createParser = function(format) {
	var funcName = "parse" + pax.date.parseFunctions.count++;
	var regexNum = pax.date.parseRegexes.length;
	var currentGroup = 1;
	pax.date.parseFunctions[format] = funcName;

	var code = "pax.date." + funcName + " = function( input ){\n"
		+ "var y = -1, m = -1, d = -1, h = -1, i = -1, s = -1;\n"
		+ "var d = new Date();\n"
		+ "y = d.getFullYear();\n"
		+ "m = d.getMonth();\n"
		+ "d = d.getDate();\n"
		+ "var results = input.match(pax.date.parseRegexes[" + regexNum + "]);\n"
		+ "if (results && results.length > 0) {";
	var regex = "";

	var special = false;
	var ch = '';
	for( var i = 0; i < format.length; ++i ) {
		ch = format.charAt( i );
		if( !special && ch == "\\" )special = true;
		else if( special ) {
			special = false;
			regex += pax.date.escape(ch);
		} else {
			obj = pax.date.formatCodeToRegex(ch, currentGroup);
			currentGroup += obj.g;
			regex += obj.s;
			if (obj.g && obj.c)code += obj.c;
		};
	}

	code += "if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0)\n"
		+ "{return new Date(y, m, d, h, i, s);}\n"
		+ "else if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0)\n"
		+ "{return new Date(y, m, d, h, i);}\n"
		+ "else if (y > 0 && m >= 0 && d > 0 && h >= 0)\n"
		+ "{return new Date(y, m, d, h);}\n"
		+ "else if (y > 0 && m >= 0 && d > 0)\n"
		+ "{return new Date(y, m, d);}\n"
		+ "else if (y > 0 && m >= 0)\n"
		+ "{return new Date(y, m);}\n"
		+ "else if (y > 0)\n"
		+ "{return new Date(y);}\n"
		+ "}return null;}";

	pax.date.parseRegexes[regexNum] = new RegExp("^" + regex + "$");
	eval(code);
};


pax.date.formatCodeToRegex = function(c, currentGroup) {
	return	(c == 'D')? { g: 0, c:null,	s:"(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)" }:
			(c == 'j' || 
			 c == 'd')? { g: 1, c: "d = parseInt(results[" + currentGroup + "], 10);\n", s:"(\\d{1,2})" }:
			(c == 'l')? { g: 0, c: null, s: "(?:" + pax.date.dayNames.join("|") + ")" }:
			(c == 'S')? { g: 0,	c: null, s: "(?:st|nd|rd|th)" }:
			(c == 'w')? { g: 0, c: null, s: "\\d" }:
			(c == 'z')? { g: 0, c: null, s: "(?:\\d{1,3})" }:
			(c == 'W')? { g: 0, c: null, s: "(?:\\d{2})" }:
			(c == 'F')? { g: 1, c: "m = parseInt(pax.date.monthNumbers[results[" + currentGroup + "].substring(0, 3)], 10);\n",	s: "(" + pax.date.monthNames.join("|") + ")" }:
			(c == 'M')? { g: 1, c: "m = parseInt(pax.date.monthNumbers[results[" + currentGroup + "]], 10);\n", s: "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)" }:
			(c == 'n' ||
			 c == 'm')? { g: 1, c: "m = parseInt(results[" + currentGroup + "], 10) - 1;\n", s: "(\\d{1,2})" }:
			(c == 't')? { g: 0, c: null, s: "\\d{1,2}" }:
			(c == 'L')? { g: 0, c: null, s: "(?:1|0)" }:
			(c == 'Y')? { g: 1, c: "y = parseInt(results[" + currentGroup + "], 10);\n", s:"(\\d{4})" }:
			(c == 'y')? { g: 1, c: "var ty = parseInt(results[" + currentGroup + "], 10);\n" + "y = ty > pax.date.y2kYear ? 1900 + ty : 2000 + ty;\n", s: "(\\d{1,2})" }:
			(c == 'a')? { g: 1, c: "if (results[" + currentGroup + "] == 'am') {\n"	+ "if (h == 12) { h = 0; }\n" + "} else { if (h < 12) { h += 12; }}", s: "(am|pm)" }:
			(c == 'A')? { g: 1, c:"if (results[" + currentGroup + "] == 'AM') {\n" + "if (h == 12) { h = 0; }\n" + "} else { if (h < 12) { h += 12; }}", s: "(AM|PM)" }:
			(c == 'g' ||
			 c == 'G' ||
			 c == 'h' ||
			 c == 'H')? { g: 1, c: "h = parseInt(results[" + currentGroup + "], 10);\n", s: "(\\d{1,2})" }:
			(c == 'i')? { g: 1,	c: "i = parseInt(results[" + currentGroup + "], 10);\n", s: "(\\d{2})" }:
			(c == 's')? { g: 1, c: "s = parseInt(results[" + currentGroup + "], 10);\n", s:"(\\d{2})" }:
			(c == 'O')? { g: 0, c: null, s: "[+-]\\d{4}" }:
			(c == 'T')? { g: 0,	c: null, s: "[A-Z]{3}" }:
			(c == 'Z')? { g: 0, c: null, s: "[+-]\\d{1,5}" }:
			{ g: 0, c: null, s:pax.date.escape(c) };
};

pax.date.getTimezone = function( date ) {
	return date.toString().replace(
		/^.*? ([A-Z]{3}) [0-9]{4}.*$/, "$1").replace(
		/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, "$1$2$3");
};

pax.date.getGMTOffset = function( date ) {
	return (date.getTimezoneOffset() > 0 ? "-" : "+")
		+ pax.date.leftPad(Math.floor(date.getTimezoneOffset() / 60), 2, "0")
		+ pax.date.leftPad(date.getTimezoneOffset() % 60, 2, "0");
};

pax.date.getDayOfYear = function( date ) {
	var num = 0;
	pax.date.daysInMonth[1] = pax.date.isLeapYear( date ) ? 29 : 28;
	for (var i = 0; i < date.getMonth(); ++i) {
		num += pax.date.daysInMonth[i];
	}
	return num + date.getDate() - 1;
};

pax.date.getWeekOfYear = function( date ) {
	// Skip to Thursday of this week, then find the first Thursday of the year
	var now = pax.date.getDayOfYear( date ) + ( 4 - date.getDay() );
	var jan1 = new Date( date.getFullYear(), 0, 1 );
	var then = ( 7 - jan1.getDay() + 4 );
	return pax.date.leftPad( ( ( now - then ) / 7 ) + 1, 2, "0" );
};

pax.date.isLeapYear = function( date ) {
	var year = date.getFullYear();
	return ( ( year & 3 ) == 0 && ( year % 100 || ( year % 400 == 0 && year ) ) );
};

pax.date.getFirstDayOfMonth = function( date ) {
	var day = ( date.getDay() - ( date.getDate() - 1 ) ) % 7;
	return ( day < 0 ) ? ( day + 7 ) : day;
};

pax.date.getLastDayOfMonth = function( date ) {
	var day = ( date.getDay() + ( pax.date.daysInMonth[ date.getMonth() ] - date.getDate() ) ) % 7;
	return ( day < 0 ) ? ( day + 7 ) : day;
};

pax.date.getDaysInMonth = function( date ) {
	pax.date.daysInMonth[1] = pax.date.isLeapYear( date ) ? 29 : 28;
	return pax.date.daysInMonth[ date.getMonth() ];
};

pax.date.getSuffix = function( date ) {
	var d = date.getDate();
	return	(d == 1 || d == 21 || d == 31 )? "st":
			(d == 2 || d == 22 )? "nd":
			(d == 3 || d == 23 )? "rd":
			"th";
};

pax.date.escape = function( string ) {
	return string.replace(/('|\\)/g, "\\$1");
};

pax.date.leftPad = function ( val, size, ch ) {
	var result = new String( val );
	if( ch == null )ch = " ";
	while( result.length < size ) {
		result = ch + result;
	}
	return result;
};

pax.date.daysInMonth = [
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
];

pax.date.monthNames = [
	"January", "February", "March", "April", "May", "June",
	"July", "August", "September", "October", "November", "December"
];

pax.date.dayNames = [
	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
];

pax.date.dayNamesShort = [
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
];

pax.date.y2kYear = 50;

pax.date.monthNumbers = {
	Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11
};

pax.date.patterns = {
	ISO8601LongPattern:"Y-m-d H:i:s",
	ISO8601ShortPattern:"Y-m-d",
	ShortDatePattern: "n/j/Y",
	LongDatePattern: "l, F d, Y",
	FullDateTimePattern: "l, F d, Y g:i:s A",
	MonthDayPattern: "F d",
	ShortTimePattern: "g:i A",
	LongTimePattern: "g:i:s A",
	SortableDateTimePattern: "Y-m-d\\TH:i:s",
	UniversalSortableDateTimePattern: "Y-m-d H:i:sO",
	YearMonthPattern: "F, Y"
};

/*	Property: pax.date.guessCfg
	Controls how the date guesser handles matches and keywords, you can override this with a different langauge, English (Australian) is default.
		
	* seperators - Seperators for the date patterns - use '*' in a datePattern, to match a seperator
	* datePatterns - What dates to try and match; order is important; have non-seperated items last. Note that these are php-style date patterns
	* keyWord - Object with keyword as key, list of allowed matches as value
*/
pax.date.guessCfg = {
	seperators: 	[ '/', '-', '.' ],
	datePatterns: 	[ 'd*m*y', 'd*m*Y', 'Y*m*d', 'j*n*y', 'j*n*Y', 'dmy', 'dmY' ],
	keyWord: {
		today: 		[ 't', 'today' ],
		tomorrow: 	[ 'tom', 'tomorrow' ],
		yesterday: 	[ 'yes', 'yesterday' ],
		next: 		[ 'n', 'next' ],
		last: 		[ 'l', 'last' ],
		day: 		{ key: [ 'd', 'day', 'days' ] },
		week:		{ key: [ 'w', 'week', 'weeks' ] },
		month:		{ key: [ 'm', 'month', 'months' ] },
		year: 		{ key: [ 'y', 'year', 'years' ] }
	}
};

//	Add days to config
for( var d = 0; d < pax.date.dayNames.length; d++ ) {
	var day = pax.date.dayNames[d].toLowerCase();
	var shortDay = day.substr(0, 3).toLowerCase();
	pax.date.guessCfg['keyWord'][pax.date.dayNames[d]] = { key: [ day, shortDay ], value: d };
}

//	Add months to config
for( var m = 0; m < pax.date.monthNames.length; m++ ) {
	var month = pax.date.monthNames[m].toLowerCase();
	var shortMonth = month.substr(0, 3).toLowerCase();
	pax.date.guessCfg['keyWord'][pax.date.monthNames[m]] = { key: [ month, shortMonth ], value: m };
}

//	Override May, as length is 3 chars, which means that by default we add the key twice...
//	This will also need to be done, if you override the guess Config with a diffreent langauge, and an item is only 3 chars.
pax.date.guessCfg['keyWord']['May'] = { 
	key: [ 'may' ], 
	value: 4 
};

/*	Method: pax.date.guess
	Populates the date obj based on the string in the input
	
	Parameters:
		input - string to guess from. There are various commands and entities that are valid:
		extraDateFormats - date formats you may want to additionally accept
		
	Input format:
		[date] [command] [entitity]
		
	Commands:
		* "next [entity]" - finds the next occurance of [entity]
		* "last [entity]" - finds the previous occurance of [entity]
		* "+ [number] [entity]" - finds the [number] next occurance of [entity]
		* "- [number] [entity]" - finds the [number] previous occurance of [entity]
		
	Entities:
		* today - gives the current date ('t' also works)
		* tomorrow - gives tomorrows date ('tom' also works)
		* yesterday - gives yesterdays date ('yes' also works)
		* weekdays - both short and long weekdays, eg: 'mon', or 'Monday' both work
		* month names - both short and long month names, eg: 'aug', or 'August' both work
		* day - 'd' and 'days' also work
		* week - 'w' and 'weeks' also work
		* month - 'm' and 'months' also work
		* year - 'y' and 'years' also work
		
	Returns:
		Either a date object populated with the guessed date, or null, if we can't guess the date
		
	Example:
		(start code)
			var myDate = pax.date.guess( 'next friday' );
		(end)
		myDate now contains the date object of next friday
		
		(start code)
			var myDate = pax.date.guess( 't +2weeks' );
		(end)
		myDate now contains the date in 2 weeks time
		(start code)
			var myDate = pax.date.guess( 'tomorrow +1month' );
		(end)
		myDate now contains the date object of 1 month from tomorrow
		(start code)
			var myDate = pax.date.guess( 'last sunday' );
		(end)
		myDate now contains the date object of last sunday
		(start code)
			var myDate = pax.date.guess( '01-01-2001 +2years' );
		(end)
		myDate now contains the date of the 1st of January 2003
		
*/
pax.date.guess = function( input, extraDateFormats ) {
	extraDateFormats = extraDateFormats || [];
	if( pax.util.getType( extraDateFormats ) != 'array' )extraDateFormats = [extraDateFormats];
	//	Split by first space or +, if no space...
	var splitAt = input.indexOf(' ');
	var splitPlus = input.indexOf('+');
	if( splitAt > -1 ) {	//	If the plus is before the space, split at it.
		if( ( splitPlus < splitAt ) && ( splitPlus > -1 ) )splitAt = splitPlus;
	} else splitAt = splitPlus;
	
	var foundDate = null;
	var bits = ( splitAt > -1 )? [input.substr(0,splitAt), input.substr(splitAt)]: [input];
	var datePart = bits[0].toLowerCase();
	var modPart = (bits[1])? bits[1].toLowerCase(): '';

	//	Config object
	var cfg = pax.date.guessCfg;

	//	Add any extra date format to the date pattern config
	if( extraDateFormats != [] ) {
		for( var i = 0; i < extraDateFormats.length; i++ ) {
			var df = extraDateFormats[i];
			if( pax.util.arrayHasValue( df, cfg['datePatterns'] ) === false ) {
				cfg['datePatterns'].push( df );
			}
		}
	}
	
	//	Match a date from the date patterns
	for( var i = 0; i < cfg['datePatterns'].length; i++ ) {
		for( var s = 0; s < cfg['seperators'].length; s++ ) {
			var df = cfg['datePatterns'][i].split('*').join( cfg['seperators'][s] );
			var matchDate = pax.date.parseDate( datePart, df );
			if( matchDate ) {
				foundDate = matchDate;
				break;
			}
		}
		if( foundDate )break;
		else {
			//	Try and match on the WHOLE string, ie: input
			for( var s = 0; s < cfg['seperators'].length; s++ ) {
				var df = cfg['datePatterns'][i].split('*').join( cfg['seperators'][s] );
				var matchDate = pax.date.parseDate( input, df );
				if( matchDate ) {
					foundDate = matchDate;
					break;
				}
			}
			/*
				Note: we took this further, and tried to match on *any* part of the input.
				Ie: break it up by spaces, and try to macth any combination. eg:
				
					'Friday March 14, 2008 08:38:00'
					
				The matches were:
				
					'Friday'
					'Friday March'
					'Friday March 14,'
					...
					'Friday March 14, 2008 08:38:00'
					       'March'
					       'March 14,'
						   'March 14, 2008'
						   ...
					       'March 14, 2008 08:38:00'
						   
				And so on. This lead to proper "guessing", as we had multiple matches.
				It was deemed that this was too complex, and would simply confuse the end user,
				after all, they want you to guess the date for them; hence that code was removed.
			*/
		}
		if( foundDate )break;		
	}
	
	//	If we didn't match a date, we must use the whole string for parsing, so we concat it here.
	if( ! foundDate )modPart = datePart + modPart;
	
	//	Split into matched elements in order.
	var keyWordFound = [];
	
	for( var k in cfg['keyWord'] ) {
		var mod = ( pax.util.getType( cfg['keyWord'][k] ) == 'object' )? cfg['keyWord'][k]['key']: cfg['keyWord'][k];
		for( var i in mod ) {
			m = mod[i];
			//	Iterate on string, and check matches
			for( var mi = 0; mi < modPart.length; mi++ ) {
				var matchStart = (modPart.substring(mi).indexOf( m ) == 0)? mi: -1;
				if( matchStart > -1 ) {
					//	Check if it's preceeded, and followed by whitespace (ie: ' ' or ''), or preceeded by a number.
					var before = (matchStart > 0)? modPart.substr( matchStart - 1, 1 ): '';
					var after  = modPart.substr( matchStart + m.length, 1 );
					if( 	( before.match( /[0-9]/g ) || before == ' ' || before == '' || before == '+' || before == '-' ) 
						&& 	( after == ' ' || after == '' || after == '+' || after == '-' ) ) {
						keyWordFound[matchStart] = k;
					}
				}
			}
		}
	}
	
	//	Numbers are special: they don't need whitespace, and we want to match ALL instances.
	for( var x = 0; x < modPart.length; x++ ) {
		var numTest = modPart.substr( x, 1 );
		// if( modPart[x].match( /[0-9]/ ) ) {
		if( numTest.match( /[0-9]/ ) ) {
			var rest = modPart.substr( x );
			keyWordFound[x] = rest.match( /[0-9]{1,}/ )[0];
			x += keyWordFound[x].length;	//	Advance the pointer by the number of chars in the number we found.
		}
	}
	
	//	Minuses are special: they don't need whitespace, and we want to match ALL instances.
	//	Plus is simply ignored, as it is the default
	for( var x = 0; x < modPart.length; x++ ) {
		if( modPart[x] == '-' )keyWordFound[x] = '-';
	}

	//	"Flatten" the data - remove any 'undefined' entries, caused by the above parsing.
	var keyWordOrder = [];
	for( var m = 0; m < keyWordFound.length; m++ ) {
		if( keyWordFound[m] )keyWordOrder.push( { index: m, value: keyWordFound[m] } );
	}
	keyWordFound = keyWordOrder;
	
	//	Now apply the keyWord
	var func = '';
	for( var i = 0; i < keyWordFound.length; i++ ) {
		var word = keyWordFound[i]['value'];
		var prevWord = [];	//	List of words prior to the current word
		var nextWord = [];	//	List of wrods after the current word
		for( var x = 0; x < i; x++ )prevWord[x] = keyWordFound[x].value;
		for( var x = i; x < keyWordFound.length; x++ )nextWord[x] = keyWordFound[x].value;
		
		if( ! foundDate ) {	//	Look for date words, if no date found yet.
			if( word == 'today' || word == 'tomorrow' || word == 'yesterday' )foundDate = new Date();
			if( word == 'tomorrow' )foundDate.setDate( foundDate.getDate() + 1 );
			else if( word == 'yesterday' )foundDate.setDate( foundDate.getDate() - 1 );
			//	if next or last, set date as 'today', allowing for entries such as 'last april', or 'next saturday'
			if( word == 'last' || word == 'next' )foundDate = new Date();
		} else {	//	Look for mod words - calculate months and days to add / subtract.
			var addDays = 0;
			var addMonths = 0;
			
			//	Check if we have 'next' or 'last' before this; it can be within 3 previous words (allowing for number and minus)
			var hasNext = ( prevWord[i -1] == 'next' || prevWord[i -2] == 'next' || prevWord[i -3] == 'next' );
			var hasLast = ( prevWord[i -1] == 'last' || prevWord[i -2] == 'last' || prevWord[i -3] == 'last' );
			
			//	If we have a day.
			var dayIndex = pax.util.arrayHasValue( word, pax.date.dayNames );
			
			if( ! ( dayIndex === false ) ) {
				//	set the date, based on the difference between the current day and the new day...
				var firstDay = parseInt( foundDate.getDay() );
				var secondDay = parseInt( dayIndex );
				var diff = (firstDay >= secondDay)? (7 - firstDay) + secondDay: secondDay - firstDay;
				
				addDays += diff;
				
				//	Check if we have a number in front, and adjust addDays
				if( ! isNaN( prevWord[i -1] ) ) {
					var myAddDays = parseInt( ( prevWord[i -2] == '-' )? -prevWord[i -1]: prevWord[i -1] ) * 7;
					if( hasLast )myAddDays *= -1;
					addDays += myAddDays;
				} else if( hasLast ) {	//	Check for 'last' - if we're on today's day, last needs to be adjusted by 14 days
					addDays -= ( diff == 7 )? 14: 7;	
				}
			}
			
			//	If we have a month
			var monthIndex = pax.util.hasValue( word, pax.date.monthNames );
			if( monthIndex ) {
				var monthHasPassed = ( foundDate.getMonth() >= monthIndex );
				foundDate.setMonth( monthIndex );
				//	Check if we have a number in front, and set addMonths
				if( ! isNaN( prevWord[i -1] ) ){
					var numMulMonths = parseInt( ( prevWord[i -2] == '-' )? -prevWord[i -1]: prevWord[i -1] );
					if( hasNext )addMonths += (12 * numMulMonths);
					if( hasLast )addMonths -= (12 * numMulMonths);
				}
				//	Check if we have next or last, and add to months, based on if the month has passed.
				if( hasNext && monthHasPassed )addMonths += 12;
				if( hasLast && (! monthHasPassed) )addMonths -= 12;
			}
			
			//	If we have 'day' or 'week'
			if( word == 'day' || word == 'week' ) {
				var multi = ( word == 'week' )? 7: 1;
				if( ! isNaN( prevWord[i -1] ) ) {
					var numAddDays = parseInt( ( prevWord[i -2] == '-' )? -prevWord[i -1]: prevWord[i -1] );
					if( hasLast )numAddDays *= -1;
					addDays += numAddDays * multi;
				} else {
					if( hasLast )addDays -= 1 * multi;
					else addDays += 1 * multi;
				}
			}
			
			//	If we have 'month' or 'year'
			if( word == 'month' || word == 'year' ) {
				var multi = ( word == 'year' )? 12: 1;
				if( ! isNaN( prevWord[i -1] ) ){
					var numAddMonths = parseInt( ( prevWord[i -2] == '-' )? -prevWord[i -1]: prevWord[i -1] );
					if( hasLast )numAddMonths *= -1;
					addMonths += numAddMonths * multi;
				} else {
					if( hasLast )addMonths -= 1 * multi;
					else addMonths += 1 * multi;
				}
			}
			
			foundDate.setDate( foundDate.getDate() + ( addDays ) );
			foundDate.setMonth( foundDate.getMonth() + ( addMonths ) );
		}
	}

	return foundDate;
};
